package com.zhangh.summer.admin.support.multipleds;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.AbstractDataSource;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * by zhanghui
 * 动态数据源
 */
@Slf4j
@Data
public class DynamicDataSource extends AbstractDataSource implements IDynamicDataSource {
    /**
     * 连接池 host+port -> druid
     */
    private static final  Map<String, DruidDataSource> DRUID_DATASOURCE_MAP = new ConcurrentHashMap();
    /**
     * 路由 dsName -> (host+port dbName)
     */
    private static final  Map<String, DatabaseDto> DS_DATABASE_MAP = new ConcurrentHashMap();

    private Environment env;

    /**
     * 默认数据源名称
     */
    private String defaultDsName;

    @PostConstruct
    public void init(){
        this.defaultDsName=env.getProperty("ds.setting.defaultDsName");
        try {
            addDsByDsName(this.defaultDsName);
        } catch (Exception e) {
            log.error("Add default ds:{} error:{}",this.defaultDsName,e.getMessage());
        }
    }


    /**
     * 添加普通数据源
     * @param dsName 数据源名称
     * @throws Exception 异常
     */
    public void addDsByDsName(String dsName) throws Exception{
        String dsType=MultiDsUtil.getDsType(dsName);
        DatabaseConfig config=new DatabaseConfig();
        config.setDsName(dsType);
        String hostPort=env.getProperty("ds.mysql."+dsName+".host")+":"+env.getProperty("ds.mysql."+dsName+".port");
        config.setHostPort(hostPort);
        config.setDbName(env.getProperty("ds.mysql."+dsName+".dbName"));
        config.setUsername(env.getProperty("ds.mysql."+dsName+".username"));
        config.setPassword(env.getProperty("ds.mysql."+dsName+".password"));
        config.setInitialSize(env.getProperty("ds.mysql."+dsName+".initialSize"));
        config.setMaxActive(env.getProperty("ds.mysql."+dsName+".maxActive"));
        config.setMinIdle(env.getProperty("ds.mysql."+dsName+".minIdle"));
        config.setMaxWait(env.getProperty("ds.mysql."+dsName+".maxWait"));
        config.setValidationQuery(env.getProperty("ds.mysql."+dsName+".validationQuery"));
        this.addDsByDatabaseConfig(config);
    }



    /**
     * 通过config添加数据源
     * @param config 配置
     * @throws Exception
     */
    public void addDsByDatabaseConfig(DatabaseConfig config) throws Exception{
        this.putDruidDataSource(config);
    }

    private void putDruidDataSource(DatabaseConfig config) {
        try {
            String hostPort=config.getHostPort();
            boolean isDsExist = false;
            for (Map.Entry<String, DruidDataSource> dsEntity : DRUID_DATASOURCE_MAP.entrySet()) {//分别设置shard的数据源
                if (dsEntity.getKey().equals(hostPort)) {
                    isDsExist = true;
                    break;
                }
            }
            if (!isDsExist) {
                DruidDataSource druidDataSource=this.initDruidDataSource(config);
                log.info("Add SupplychainDS DruidDataSource key:{} DruidDataSource.url:{}", hostPort, druidDataSource.getUrl());
                DRUID_DATASOURCE_MAP.put(hostPort, druidDataSource);
                DatabaseDto databaseDto=new DatabaseDto();
                databaseDto.setUrl(hostPort);
                databaseDto.setDbName(config.getDbName());
                DS_DATABASE_MAP.put(config.getDsName(),databaseDto);

            }
        }catch (Exception e){
            e.printStackTrace();
            log.error("putDruidDataSource error:{}",e.getMessage());
        }
    }

    /**
     * 获得 DruidDatasource
     * @return
     * @throws Exception
     */
    public Map<String, DruidDataSource> getDruidDatasourceMap() {
        return DRUID_DATASOURCE_MAP;
    }


    private DruidDataSource initDruidDataSource(DatabaseConfig config){
        DruidDataSource ds=new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        String dbName="";
        if (StringUtils.isNotBlank(config.getDbName())){
            dbName=config.getDbName();
        }
        String url = "jdbc:mysql://"+config.getHostPort()+"/"+dbName+"?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull" ;
        ds.setUrl(url);
        ds.setUsername(config.getUsername());
        ds.setPassword(config.getPassword());
        ds.setInitialSize(config.getInitialSize() != null ? Integer.valueOf(config.getInitialSize()) : 5);
        ds.setMaxActive(config.getMaxActive() != null ? Integer.valueOf(config.getMaxActive()) : 150);
        ds.setMinIdle(config.getMinIdle() != null ? Integer.valueOf(config.getMinIdle()) : 10);
        ds.setMaxWait(config.getMaxWait() != null ? Long.valueOf(Integer.valueOf(config.getMaxWait())) : 10000L);
        ds.setValidationQuery(config.getValidationQuery() != null && !"".equals(config.getValidationQuery()) ? config.getValidationQuery() : "SELECT 1");
        ds.setValidationQueryTimeout(1000);
        ds.setMinEvictableIdleTimeMillis(60000L);
        ds.setTimeBetweenEvictionRunsMillis(60000L);
        ds.setTestWhileIdle(true);
        ds.setRemoveAbandoned(true);
        ds.setRemoveAbandonedTimeout(80);
        ds.setLogAbandoned(true);
        ds.setTestOnBorrow(true);
        ds.setTestOnReturn(false);
        ds.setKeepAlive(true);
        return ds;
    }



    @PostConstruct
    private void initDefaultDs(){
        try{
            String  defaultDsName=env.getProperty("ds.setting.defaultDsName");
            addDsByDsName(defaultDsName);
            DataSourceContextHolder.setDataSourceType(MultiDsUtil.getDsType(defaultDsName));
        }catch (Exception e){
            log.error("add default ds error");
            e.printStackTrace();
        }
    }

    @Override
    public Connection getConnection() throws SQLException {
        DataSource dataSource = null;
            String dbName = "";
            String dataSourceType = DataSourceContextHolder.getDataSourceType();
            if (StringUtils.isBlank(dataSourceType)) {
                if (StringUtils.isNotEmpty(defaultDsName)) {
                    dataSourceType = MultiDsUtil.getDsType(defaultDsName);
                    DataSourceContextHolder.setDataSourceType(dataSourceType);
                    log.info("thread local ds is null.use default ds:{}", dataSourceType);
                } else {
                    throw new RuntimeException("Please set datasource type first!");
                }
            }
            if (!DS_DATABASE_MAP.containsKey(dataSourceType)) {
                throw new RuntimeException("Datasource type error!  ds key: " + dataSourceType);
            } else {
                DatabaseDto databaseDto = DS_DATABASE_MAP.get(dataSourceType);
                dbName = databaseDto.getDbName();
                dataSource = DRUID_DATASOURCE_MAP.get(databaseDto.getUrl());
                if (dataSource == null) {
                    throw new RuntimeException("Local key " + dataSourceType + " not exist!");
                } else {
                    Connection connection = dataSource.getConnection();
                    connection.setCatalog(dbName);
                    return connection;
                }
            }
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        throw new UnsupportedOperationException("Could not get datasource by username!");
    }

}
